﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Web;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.IO.Compression;
using System.Web.Caching;

using ComLib.Web;
using ComLib.Extensions;


namespace ComLib.Mvc.Lib.Handlers
{
	/// <summary>
	/// Removes whitespace in all stylesheets added to the 
	/// header of the HTML document in site.master. 
	/// </summary>
	public class CssHandler : IHttpHandler
	{
        private static IDictionary<string, string> _replaceChars = new Dictionary<string, string>();
        private static IDictionary _config = new Dictionary<string, object>();
        private static string _sectionName = "WebHandlers.Css";


        static CssHandler()
        {
            // All the replacement chars for removing whitespace from css files.
            _replaceChars["  "] = " ";            
            _replaceChars[Environment.NewLine] = String.Empty;
            _replaceChars["\t"] = string.Empty;
            _replaceChars[" {"] = "{";
            _replaceChars[" :"] = ":";
            _replaceChars[": "] = ":";
            _replaceChars[", "] = ",";
            _replaceChars["; "] = ";";
            _replaceChars[";}"] = "}";
            _replaceChars[@"?"] = string.Empty;            
        }


        /// <summary>
        /// Set the configuration settings as an IDictionary.
        /// </summary>
        /// <param name="config"></param>
        public static void Init(IDictionary config, string sectionname, bool useSection)
        {
            _config = config;
            if (useSection)
                _sectionName = sectionname;
        }

            
		/// <summary>
		/// Enables processing of HTTP Web requests by a custom 
		/// HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"></see> interface.
		/// </summary>
		/// <param name="context">An <see cref="T:System.Web.HttpContext"></see> object that provides 
		/// references to the intrinsic server objects 
		/// (for example, Request, Response, Session, and Server) used to service HTTP requests.
		/// </param>
		public void ProcessRequest(HttpContext context)
		{
			if (!string.IsNullOrEmpty(context.Request.QueryString["name"]))
			{
				string fileName = context.Request.QueryString["name"];
				string css = string.Empty;

				// 1. Check for only .css files
				if (!fileName.EndsWith("css", StringComparison.OrdinalIgnoreCase))
					throw new System.Security.SecurityException("Invalid CSS file extension");

                // 2. Get the css file.
                css = GetCssFile(fileName);

				// 3. Validate .css file.
				if (string.IsNullOrEmpty(css))
				{
                    context.Response.Status = "Css not found";
                    context.Response.StatusCode = 404;
                    return;
                }

				// Have valid css file at this point.
                // Now set the headers for caching the .css on the client side.
				SetHeaders(css.GetHashCode(), context);
				context.Response.Write(css);                    
			}
		}


        /// <summary>
        /// Get the css file.
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private string GetCssFile(string fileName)
        {
            string css = string.Empty;
            if (fileName.StartsWith("http", StringComparison.OrdinalIgnoreCase))
            {
                css = WebUtils.GetFileContentsRemote(fileName);
            }
            else
            {
                css = WebUtils.GetFileContentsLocal(fileName);
            }
            if (_config.Get<bool>(_sectionName, "RemoveWhiteSpace", true))
            {
                css = RemoveWhitespace(css);
            }
            return css;
        }


		/// <summary>
		/// Removes whitespace from .css file.
		/// </summary>
        private string RemoveWhitespace(string body)
		{
            // Replace all the replacement chars.
            foreach (var entry in _replaceChars)
            {
                body = body.Replace(entry.Key, entry.Value);
            }

			// Remove comments
			body = Regex.Replace(body, @"/\*[\d\D]*?\*/", string.Empty);

			return body;
		}


		/// <summary>
		/// This will make the browser and server keep the output
		/// in its cache and thereby improve performance.
		/// </summary>
        private static void SetHeaders(int hash, HttpContext context)
        {
            bool isCachable = _config.Get<bool>(_sectionName, "CacheEnabledOnClientSide", true);
            context.Response.ContentType = "text/css";
            context.Response.Cache.VaryByHeaders["Accept-Encoding"] = true;

            if (isCachable)
            {
                int maxDaysCached = _config.Get<int>(_sectionName, "CacheDurationDays", 10);
                context.Response.Cache.SetCacheability(HttpCacheability.Public);
                context.Response.Cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(maxDaysCached));
                context.Response.Cache.SetMaxAge(new TimeSpan(maxDaysCached, 0, 0, 0));

                string eTag = "\"" + hash.ToString() + "\"";
                string existingEtag = context.Request.Headers["If-None-Match"];
                context.Response.Cache.SetETag(eTag);

                if (String.Compare(existingEtag, eTag) == 0)
                {
                    context.Response.Clear();
                    context.Response.StatusCode = (int)System.Net.HttpStatusCode.NotModified;
                    context.Response.SuppressContent = true;
                }
            }
        }		


		/// <summary>
		/// Gets a value indicating whether another request can use the <see cref="T:System.Web.IHttpHandler"></see> instance.
		/// </summary>
		/// <value></value>
		/// <returns>true if the <see cref="T:System.Web.IHttpHandler"></see> instance is reusable; otherwise, false.</returns>
		public bool IsReusable
		{
			get { return false; }
		}
	}
}
